using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Xml;

namespace PatchMakingTool
{
    public partial class MainForm : Form
    {
        private Thread threadDoCreate;
        private delegate void DoCreateUpdateProcessCallback(string message);
        private delegate void DoCreateProcessComplete();

        public MainForm()
        {
            InitializeComponent();
            ucSelectFileOldVersion.FileSelected += new EventHandler<FileSelectedEventArgs>(ucSelectFile1_FileSelected);
            ucSelectFileNewVersion.FileSelected += new EventHandler<FileSelectedEventArgs>(ucSelectFile2_FileSelected);
            ucSelectFilePatch.FileSelected += new EventHandler<FileSelectedEventArgs>(ucSelectFile3_FileSelected);
            ucSelectFolderOutput.FolderSelected += new EventHandler<FolderSelectedEventArgs>(ucSelectFolder2_FolderSelected);
            ucSelectFolderTemp.FolderSelected += new EventHandler<FolderSelectedEventArgs>(ucSelectFolderTemp_FolderSelected);
        }

        void ucSelectFolderTemp_FolderSelected(object sender, FolderSelectedEventArgs e)
        {
            if (checkBoxTempOutput.Checked)
            {
                ucSelectFolderOutput.FolderPath = e.FolderPath;
            }
        }

        /// <summary>
        /// Main Function create a msp file
        /// </summary>
        void DoCreate()
        {
            #region Init parameters
            string oldVersionMsi = this.ucSelectFileOldVersion.FilePath;

            string newVersionMsi = this.ucSelectFileNewVersion.FilePath;

            string outputPath = this.ucSelectFolderOutput.FolderPath;

            string dirExtractOldVersion = Path.Combine(outputPath,
                Path.GetFileNameWithoutExtension(oldVersionMsi));

            string dirExtractNewVersion = Path.Combine(outputPath,
                Path.GetFileNameWithoutExtension(newVersionMsi));

            string oldVersionMsiExtractedFile = Path.Combine(dirExtractOldVersion,
                Path.GetFileName(oldVersionMsi));

            string newVersionMsiExtractedFile = Path.Combine(dirExtractNewVersion,
                Path.GetFileName(newVersionMsi));

           string  patchWxs = this.ucSelectFilePatch.FilePath;

            string patchName = Path.GetFileNameWithoutExtension(this.textBoxPatchName.Text); //Path.GetFileNameWithoutExtension(patchWxs);

            string patchWixobj = Path.Combine(Path.GetDirectoryName(patchWxs),
                string.Format("{0}.wixobj", patchName));

            string patchPcp = Path.Combine(outputPath,
                string.Format("{0}.pcp", patchName));

            string patchMsp = Path.Combine(outputPath,
                string.Format("{0}.msp", patchName));

            string patchLog = Path.Combine(outputPath,
                string.Format("{0}.log", patchName));

            string parameterMsiexecOld = string.Format("/a \"{0}\" TARGETDIR=\"{1}\" /passive /norestart", oldVersionMsi, dirExtractOldVersion);
            string parameterMsiexecNew = string.Format("/a \"{0}\" TARGETDIR=\"{1}\" /passive /norestart", newVersionMsi, dirExtractNewVersion);
            string parameterCandle = string.Format("\"{0}\" -out \"{1}\"", patchWxs, patchWixobj);
            string parameterLight = string.Format("\"{0}\" -out \"{1}\"", patchWixobj, patchPcp);
            string parameterMsimsp = string.Format("-s \"{0}\" -p \"{1}\" -l \"{2}\"", patchPcp, patchMsp, patchLog);
            #endregion

            this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                new object[] { "===" + DateTime.Now.ToString() + "==="});

            #region Process Build Msp
            Thread.Sleep(1000);

            //Administrative Installer 1
            this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                new object[] { "==Administrative Old version==" + Environment.NewLine });

            Thread.Sleep(1000);
            this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                new object[] { "msiexec " + parameterMsiexecOld + Environment.NewLine });

            Process processOldVerion = NewProcess("msiexec", parameterMsiexecOld);
            processOldVerion.Start();
            processOldVerion.WaitForExit();

            Thread.Sleep(1000);

            if (processOldVerion.HasExited)
            {
                //Administrative Installer 2
                this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                    new object[] { "==Administrative New version==" + Environment.NewLine });

                Thread.Sleep(1000);
                this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                    new object[] { "msiexec " + parameterMsiexecNew + Environment.NewLine });

                Process processNewVersion = NewProcess("msiexec", parameterMsiexecNew);
                processNewVersion.Start();
                processNewVersion.WaitForExit();

                Thread.Sleep(1000);

                if (processNewVersion.HasExited)
                {
                    // Update Wix Patch Information
                    // Init list
                    List<MyElement> list = new List<MyElement>() {
                        new MyElement("PatchCreation", "Id", Guid.NewGuid().ToString()),
                        new MyElement("UpgradeImage", "src", newVersionMsiExtractedFile),
                        new MyElement("TargetImage", "src", oldVersionMsiExtractedFile)};
                    
                    UpdateWixPatchProjectInformation(patchWxs, list);

                    //Build Wix Patch
                    this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                        new object[] { "==Build Wix Patch==" + Environment.NewLine });

                    this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                        new object[] { "candle " + parameterCandle + Environment.NewLine });

                    Process processCandle = NewProcess("candle", parameterCandle);
                    processCandle.Start();
                    processCandle.WaitForExit();

                    Thread.Sleep(1000);

                    if (processCandle.HasExited)
                    {
                        this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                            new object[] { "light " + parameterLight + Environment.NewLine });

                        Process processLight = NewProcess("light", parameterLight);
                        processLight.Start();
                        processLight.WaitForExit();

                        Thread.Sleep(1000);

                        if (processLight.HasExited)
                        {
                            // Cretae Msp Update Patch
                            this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                                new object[] { "==Cretae Msp Update Patch==" + Environment.NewLine });

                            this.Invoke(new DoCreateUpdateProcessCallback(UpdateProcess),
                                new object[] { "msimsp " + parameterMsimsp + Environment.NewLine });

                            Process processMsimsp = NewProcess("msimsp", parameterMsimsp);
                            processMsimsp.Start();
                            processMsimsp.WaitForExit();

                            Thread.Sleep(1000);

                            if (processMsimsp.HasExited)
                            {
                                // Process completed
                                this.Invoke(new DoCreateProcessComplete(ProcessComplete),
                                    new object[] { });

                                //Finish
                                threadDoCreate.Abort();

                            }//Process Msimsp
                        }//Process Light
                    }//Process Candle
                }//Process NewVersion
            }//Process OldVersion
            #endregion
        }

        /// <summary>
        /// Init a process.
        /// </summary>
        /// <param name="executor">The executor.</param>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        Process NewProcess(string executor, string parameter)
        {
            ProcessStartInfo startInfor = new ProcessStartInfo();
            startInfor.Arguments = parameter;
            startInfor.FileName = executor;

            Process p = new Process();
            p.StartInfo = startInfor;

            return p;
        }

        /// <summary>
        /// Inform when Process has changes.
        /// </summary>
        /// <param name="message">The message.</param>
        void UpdateProcess(string message)
        {
            this.textBoxOutput.Text += message + Environment.NewLine;
        }

        /// <summary>
        /// Inform when Processes is completed.
        /// </summary>
        private void ProcessComplete()
        {
            threadDoCreate.Abort();     

            this.textBoxOutput.Text += Environment.NewLine + "Complete.";
            this.textBoxOutput.Text += Environment.NewLine + "===" + DateTime.Now.ToString() + "===";
            this.buttonCreate.Enabled = true;
        }

        private void UpdateWixPatchProjectInformation(string projectFile, List<MyElement> list)
        {
            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(projectFile);

            foreach (MyElement e in list)
            {
                XmlNodeList nodes = xDoc.DocumentElement.GetElementsByTagName(e.ElementName);
                foreach (XmlNode n in nodes)
                {
                    n.Attributes[e.AttributeName].Value = e.AttributeValue;
                }
            }

            xDoc.Save(projectFile);
        }

        void ucSelectFolder2_FolderSelected(object sender, FolderSelectedEventArgs e)
        {
            if(!string.IsNullOrEmpty(e.FolderPath))
                this.buttonOpenOutput.Enabled = true;
        }

        void ucSelectFile3_FileSelected(object sender, FileSelectedEventArgs e)
        {
            this.textBoxPatchName.Text = Path.GetFileName(e.FilePath);
        }

        void ucSelectFile2_FileSelected(object sender, FileSelectedEventArgs e)
        {
            this.labelFileName2.Text = Path.GetFileName(e.FilePath);
        }

        void ucSelectFile1_FileSelected(object sender, FileSelectedEventArgs e)
        {
            this.labelFileName1.Text = Path.GetFileName(e.FilePath);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.buttonOpenOutput.Enabled = false;
        }

        private void buttonReset_Click(object sender, EventArgs e)
        {
            this.ucSelectFileOldVersion.Reset();
            this.ucSelectFileNewVersion.Reset();
            this.ucSelectFilePatch.Reset();
            this.ucSelectFolderTemp.Reset();
            this.ucSelectFolderOutput.Reset();
            this.textBoxOutput.Text = string.Empty;
        }

        private void buttonOpenOutput_Click(object sender, EventArgs e)
        {
            ProcessStartInfo startInfor = new ProcessStartInfo();
            startInfor.FileName = ucSelectFolderOutput.FolderPath;
            startInfor.UseShellExecute = true;
            startInfor.Verb = "open";

            Process processOpenOutput = new Process();
            processOpenOutput.StartInfo = startInfor;
            processOpenOutput.Start();
            
        }

        private void buttonCreate_Click(object sender, EventArgs e)
        {
            if (CheckInputIsValid())
            {
                this.buttonCreate.Enabled = false;

                threadDoCreate = new Thread(DoCreate);
                threadDoCreate.Start();
            }
            else
            {
                MessageBox.Show(
                    "Please input all values.",
                    "Warning",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
            }
        }

        private bool CheckInputIsValid()
        {
            if (string.IsNullOrEmpty(ucSelectFileOldVersion.FilePath))
                return false;
            if (string.IsNullOrEmpty(ucSelectFileNewVersion.FilePath))
                return false;
            if (string.IsNullOrEmpty(ucSelectFilePatch.FilePath))
                return false;
            if (string.IsNullOrEmpty(ucSelectFolderTemp.FolderPath))
                return false;
            if (string.IsNullOrEmpty(ucSelectFolderOutput.FolderPath))
                return false;
            if (!File.Exists(ucSelectFileOldVersion.FilePath))
                return false;
            if (!File.Exists(ucSelectFileNewVersion.FilePath))
                return false;
            if (!File.Exists(ucSelectFilePatch.FilePath))
                return false;

            return true;
        }

        private void buttonClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void checkBoxAutoSelectTemp_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBoxAutoSelectTemp.Checked)
            {
                ucSelectFolderTemp.FolderPath = Path.GetTempPath();
            }
        }
    }
}